﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Storage;

namespace BenevolentSun
{
    /// <summary>
    /// A static singleton to hold the current session game state. Eventually this class will be able to be initialized and loaded with game state.
    /// </summary>
    class Session
    {

        public static event EventHandler OnLoaded;

        public static event EventHandler OnSaved;

        public static event EventHandler OnRecordsSaved;

        public static event EventHandler OnRecordsLoaded;


        #region Static Singleton
        private static Session instance;
        private static Session Singleton
        {
            get
            {
                if (instance == null)
                    throw (new Exception("No Session is active!"));
                return instance;
            }
            set
            {
                if (value != null && instance != null)
                    throw (new Exception("Session is already active!"));
                instance = value;
            }
        }
        #endregion


        #region State
        bool isMusicMuted = false;

        public static Boolean IsMusicMuted
        {
            get { return Singleton.isMusicMuted; }
            set { 
                Singleton.isMusicMuted = value;
                if (value)
                    AudioManager.SetMusicVolume(0f);
                else
                    AudioManager.SetMusicVolume(1f);
            }
        }


        public static bool IsActive
        {
            get { return instance != null; }
        }

        uint score = 0;
        public static uint Score
        {
            get { return Singleton.score; }
            set { Singleton.score = value; }
        }

        private float ambientPower = 15f;
        public static float AmbientPower
        {
            get { return Singleton.ambientPower; }
            set { Singleton.ambientPower = Math.Max(value, 0f); }
        }
        #endregion


        #region Levels
        private int level;
        public static int Level
        {
            get { return Singleton.level; }
        }

        public static void CompleteLevel(int levelNum)
        {
            if (levelNum == Singleton.level)
                Singleton.level++;
            if (Singleton.level == BenevolentSun.LevelManifest.Levels.Count)
            {
                Singleton.isGameComplete = true;
            }
        }

        public bool isGameComplete;
        public static bool IsGameComplete
        {
            get { return Singleton.isGameComplete; }
        }
        #endregion


        #region Records
        List<Record> records;
        #endregion


        #region Starting a Session
        public static void StartNewSession()
        {
            Singleton = new Session();
        }

        private Session(){
            level = 1;
        }
        #endregion


        #region Loading Session / Records
        public static void LoadSession()
        {
            // end any existing session
            if (IsActive)
                EndSession();

            // create the new session
            Singleton = new Session();

            // get the storage device and load the session
            GetStorageDevice(
                delegate(StorageDevice storageDevice)
                {
                    LoadSessionResult(storageDevice);
                }, PlayerIndex.One);
        }

        public static void LoadSessionResult(StorageDevice storageDevice)
        {
            // check the parameter
            if ((storageDevice == null) || !storageDevice.IsConnected)
            {
                return;
            }

            // open the container
            using (StorageContainer storageContainer = storageDevice.OpenContainer(Session.SaveGameContainerName))
            {
                string filename = Path.Combine(storageContainer.Path, Session.SaveGameFilename);
                if (File.Exists(filename))
                {
                    using (FileStream stream = new FileStream(filename, FileMode.Open))
                    {
                        using (XmlReader xmlReader = XmlReader.Create(stream))
                        {
                            // <switchSaveData>
                            xmlReader.ReadStartElement("switchSaveData");

                            SaveData saveData = new XmlSerializer(typeof(SaveData)).Deserialize(xmlReader) as SaveData;
                            Singleton.level = saveData.Level;
                            Singleton.isGameComplete = saveData.IsGameComplete;
                            IsMusicMuted = saveData.IsMusicMuted;

                            // </switchSaveData>
                            xmlReader.ReadEndElement();
                        }
                    }
                }
                else
                {
                    EndSession();
                    StartNewSession();
                }

                storageContainer.Dispose();
            }

            if (OnLoaded != null)
            {
                Console.WriteLine("done loading cast");
                OnLoaded(Singleton, EventArgs.Empty);
            }
        }

        public static void LoadRecords()
        {
            // get the storage device and load the session
            GetStorageDevice(
                delegate(StorageDevice storageDevice)
                {
                    LoadRecordsResult(storageDevice);
                });
        }

        public static void LoadRecordsResult(StorageDevice storageDevice)
        {
            // check the parameter
            if ((storageDevice == null) || !storageDevice.IsConnected)
            {
                return;
            }

            // open the container
            using (StorageContainer storageContainer = storageDevice.OpenContainer(Session.SaveGameContainerName))
            {
                string filename = Path.Combine(storageContainer.Path, Session.SaveRecordsFilename);
                if (File.Exists(filename))
                {
                    using (FileStream stream = new FileStream(filename, FileMode.Open))
                    {
                        using (XmlReader xmlReader = XmlReader.Create(stream))
                        {
                            // <switchSaveData>
                            xmlReader.ReadStartElement("switchRecordsData");

                            RecordsSaveData saveData = new XmlSerializer(typeof(RecordsSaveData)).Deserialize(xmlReader) as RecordsSaveData;
                            Singleton.records = saveData.Records;

                            // </switchSaveData>
                            xmlReader.ReadEndElement();
                        }
                    }
                }
                else
                {
                    Singleton.records = new List<Record>();
                }

                storageContainer.Dispose();
            }

            if (OnRecordsLoaded != null)
            {
                OnRecordsLoaded(Singleton, EventArgs.Empty);
            }
        }
        #endregion


        #region Saving a Session
        public static void SaveSession()
        {
            // retrieve the storage device, asynchronously
            GetStorageDevice(delegate(StorageDevice storageDevice)
            {
                SaveSessionResult(storageDevice);
            }, PlayerIndex.One);
        }

        private static void SaveSessionResult(StorageDevice storageDevice)
        {
            // check the parameter
            if ((storageDevice == null) || !storageDevice.IsConnected)
            {
                return;
            }

            // open the container
            using (StorageContainer storageContainer = storageDevice.OpenContainer(Session.SaveGameContainerName))
            {
                string filename = Path.Combine(storageContainer.Path, Session.SaveGameFilename);
                
                using (FileStream stream = new FileStream(filename, FileMode.Create))
                {
                    using (XmlWriter xmlWriter = XmlWriter.Create(stream))
                    {
                        // <switchSaveData>
                        xmlWriter.WriteStartElement("switchSaveData");

                        SaveData saveData = new SaveData();
                        saveData.Level = Level;
                        saveData.IsGameComplete = IsGameComplete;
                        saveData.IsMusicMuted = IsMusicMuted;
                        new XmlSerializer(typeof(SaveData)).Serialize(xmlWriter, saveData);

                        // </switchSaveData>
                        xmlWriter.WriteEndElement();
                    }
                }

                storageContainer.Dispose();
            }

            if (OnSaved != null)
                OnSaved(Singleton, EventArgs.Empty);
        }

        public static void SaveRecords()
        {
            // retrieve the storage device, asynchronously
            GetStorageDevice(delegate(StorageDevice storageDevice)
            {
                SaveRecordsResult(storageDevice);
            });
        }

        private static void SaveRecordsResult(StorageDevice storageDevice)
        {
            // check the parameter
            if ((storageDevice == null) || !storageDevice.IsConnected)
            {
                return;
            }

            // open the container
            using (StorageContainer storageContainer = storageDevice.OpenContainer(Session.SaveGameContainerName))
            {
                string filename = Path.Combine(storageContainer.Path, Session.SaveRecordsFilename);

                using (FileStream stream = new FileStream(filename, FileMode.Create))
                {
                    using (XmlWriter xmlWriter = XmlWriter.Create(stream))
                    {
                        // <switchSaveData>
                        xmlWriter.WriteStartElement("switchRecordsData");

                        RecordsSaveData saveData = new RecordsSaveData();
                        saveData.Records = Singleton.records;
                        new XmlSerializer(typeof(RecordsSaveData)).Serialize(xmlWriter, saveData);

                        // </switchSaveData>
                        xmlWriter.WriteEndElement();
                    }
                }

                storageContainer.Dispose();
            }

            if (OnRecordsSaved != null)
                OnRecordsSaved(Singleton, EventArgs.Empty);
        }
        #endregion


        #region Storage
        /// <summary>
        /// The container name used for save games.
        /// </summary>
        public static string SaveGameContainerName = "Switch";

        public static string SaveGameFilename = "SwitchSaveGame.xml";

        public static string SaveRecordsFilename = "SwitchSaveRecords.xml";

        /// <summary>
        /// The stored StorageDevice object.
        /// </summary>
        private static StorageDevice storageDevice;
        private static StorageDevice allPlayerStorageDevice;

        /// <summary>
        /// A delegate for receiving StorageDevice objects.
        /// </summary>
        public delegate void StorageDeviceDelegate(StorageDevice storageDevice);

        /// <summary>
        /// Asynchronously retrieve a storage device.
        /// </summary>
        /// <param name="retrievalDelegate">
        /// The delegate called when the device is available.
        /// </param>
        /// <remarks>
        /// If there is a suitable cached storage device, 
        /// the delegate may be called directly by this function.
        /// </remarks>
        public static void GetStorageDevice(StorageDeviceDelegate retrievalDelegate, PlayerIndex pi)
        {
            // check the parameter
            if (retrievalDelegate == null)
            {
                throw new ArgumentNullException("retrievalDelegate");
            }

            // check the stored storage device
            if ((storageDevice != null) && storageDevice.IsConnected)
            {
                retrievalDelegate(storageDevice);
                return;
            }

            // the storage device must be retrieved
            storageDevice = null;
            Guide.BeginShowStorageDeviceSelector(pi, GetStorageDeviceResult, retrievalDelegate);
        }

        public static void GetStorageDevice(StorageDeviceDelegate retrievalDelegate)
        {
            // check the parameter
            if (retrievalDelegate == null)
            {
                throw new ArgumentNullException("retrievalDelegate");
            }

            // check the stored storage device
            if ((allPlayerStorageDevice != null) && allPlayerStorageDevice.IsConnected)
            {
                retrievalDelegate(allPlayerStorageDevice);
                return;
            }

            // the storage device must be retrieved
            allPlayerStorageDevice = null;
            Guide.BeginShowStorageDeviceSelector(GetAllPlayerStorageDeviceResult, retrievalDelegate);
        }

        

        /// <summary>
        /// Asynchronous callback to the guide's BeginShowStorageDeviceSelector call.
        /// </summary>
        /// <param name="result">The IAsyncResult object with the device.</param>
        private static void GetStorageDeviceResult(IAsyncResult result)
        {
            // check the parameter
            if ((result == null) || !result.IsCompleted)
            {
                return;
            }

            // retrieve and store the storage device
            storageDevice = Guide.EndShowStorageDeviceSelector(result);

            // check the new storage device 
            if ((storageDevice != null) && storageDevice.IsConnected)
            {
                // it passes; call the stored delegate
                StorageDeviceDelegate func = result.AsyncState as StorageDeviceDelegate;
                if (func != null)
                {
                    func(storageDevice);
                }
            }
        }

        private static void GetAllPlayerStorageDeviceResult(IAsyncResult result)
        {
            // check the parameter
            if ((result == null) || !result.IsCompleted)
            {
                return;
            }

            // retrieve and store the storage device
            allPlayerStorageDevice = Guide.EndShowStorageDeviceSelector(result);

            // check the new storage device 
            if ((allPlayerStorageDevice != null) && allPlayerStorageDevice.IsConnected)
            {
                // it passes; call the stored delegate
                StorageDeviceDelegate func = result.AsyncState as StorageDeviceDelegate;
                if (func != null)
                {
                    func(allPlayerStorageDevice);
                }
            }
        }
        #endregion


        #region Ending a Session
        public static void EndSession()
        {
            Singleton = null;
        }
        #endregion

    }

    public class Record
    {
        public string playerName;

        public float timeMs;
    }

    public class RecordsSaveData
    {
        public List<Record> Records;
    }

    public class SaveData
    {
        public int Level;

        public bool IsGameComplete;

        public bool IsMusicMuted;
    }

}
